package com.xuzz.study.spring.simpleioc.core;

import com.xuzz.study.spring.simpleioc.bean.BeanDefinition;
import com.xuzz.study.spring.simpleioc.bean.ConstructorArg;
import com.xuzz.study.spring.simpleioc.utils.BeanUtils;
import com.xuzz.study.spring.simpleioc.utils.ClassUtils;
import com.xuzz.study.spring.simpleioc.utils.ReflectionUtils;
import org.apache.commons.lang3.StringUtils;

import java.lang.reflect.Field;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

public class BeanFactoryImpl implements BeanFactory {

    /**
     * 我们有两 HashMap，beanMap 和 beanDefineMap。 beanDefineMap 存储的是对象的名称和对象对应的数据结构的映射。beanMap 用于保存 beanName和实例化之后的对象。
     *
     * 容器初始化的时候，会调用 BeanFactoryImpl.registerBean 方法。把 对象的 BeanDefination 数据结构，存储起来。
     *
     * 当我们调用 getBean() 的方法的时候。会先到 beanMap 里面查找，有没有实例化好的对象。如果没有，就会去beanDefineMap查找这个对象对应的 BeanDefination。再利用DeanDefination去实例化一个对象。
     *
     * 对象实例化成功以后，我们还需要注入相应的参数，调用 populatebean()这个方法。在 populateBean 这个方法中，会扫描对象里面的Field，如果对象中的 Field 是我们IoC容器管理的对象，那就会调用 我们上文实现的 ReflectionUtils.injectField来注入对象。
     *
     * 一切准备妥当之后，我们对象就完成了整个 IoC 流程。最后这个对象放入 beanMap 中,方便下一次使用。
     *
     * 所以我们可以知道 BeanFactory 是管理和生成对象的地方。
     */

    private static final ConcurrentHashMap<String, BeanDefinition> beanDefineMap=new ConcurrentHashMap<>();
    private static final ConcurrentHashMap<String,Object> beanMap = new ConcurrentHashMap<>();
    private static final Set<String> beanNameSet = Collections.synchronizedSet(new HashSet<>());

    //用于解决循环依赖
    private final Map<String, Object> earlySingletonObjects = new HashMap<String, Object>(16);

    @Override
    public Object getBean(String name) throws Exception {
        Object bean = beanMap.get(name);
        if(null!=bean)
        {
            return bean;
        }

        Object earlyBean = earlySingletonObjects.get(name);
        if (earlyBean != null) {
            System.out.println("循环依赖，提前返回尚未加载完成的bean:" + name);
            return earlyBean;
        }

        bean = createBean(beanDefineMap.get(name));
        if(null!=bean)
        {
            earlySingletonObjects.put(name, bean);
            //注入
            populateBean(bean);
            beanMap.put(name,bean);
            earlySingletonObjects.remove(name);
        }

        return bean;
    }

    public void registerBean(String name,BeanDefinition beanDefinition)
    {
        beanDefineMap.put(name,beanDefinition);
        beanNameSet.add(name);
    }

    private Object createBean(BeanDefinition bd) throws Exception
    {
        String beanName = bd.getName();
        Class clz = ClassUtils.findClass(bd.getClassName());
        if(null==clz)
        {
            throw new Exception("can not find bean by beanName");
        }
        List<ConstructorArg> constructorArgs = bd.getConstructorArgs();
        if(null!=constructorArgs || constructorArgs.size()>0)
        {
            List<Object> objects = new ArrayList<>();
            for(ConstructorArg constructorArg:constructorArgs)
            {
                objects.add(getBean(constructorArg.getRef()));
            }
            return BeanUtils.instanceByCglib(clz,clz.getConstructor(),objects.toArray());
        }
        else
        {
            return BeanUtils.instanceByCglib(clz,null,null);
        }
    }

    private void populateBean(Object bean) throws Exception
    {
        Field[] fields = bean.getClass().getSuperclass().getDeclaredFields();
        if (fields != null && fields.length > 0) {
            for(Field field:fields)
            {
                String beanName = field.getName();
                beanName= StringUtils.uncapitalize(beanName);
                if(beanNameSet.contains(field.getName()))
                {
                    Object fieldBean = getBean(beanName);
                    if(null!=fieldBean)
                    {
                        ReflectionUtils.injectField(field,bean,fieldBean);
                    }
                }
            }
        }
    }

}
